package com.blue.http.controller;

import com.blue.integration.spring.redis.RedisCache;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.redis.core.BoundHashOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.util.Assert;

import javax.annotation.Resource;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

/**
 * Redis实现的认证配置
 *
 * @author zhengj
 * @since 1.0 2017年3月6日
 */
public abstract class RedisAuthorized implements Authorized
{
	private static Logger logger = LoggerFactory.getLogger(RedisAuthorized.class);

	private static final String PREFIX = "com.blue.http:";
	private static final String ACCOUNT_NAME = PREFIX + "account_name";
	private static final String ACCOUNT_ROLE = PREFIX + "account_role";
	private static final String ROLE_URL = PREFIX + "role_url";

	private static final String ACCOUNT_TOKEN = PREFIX + "account_token_";
	private static final String TOKEN_ACCOUNT = PREFIX + "token_account_";

	@Resource
	private RedisCache redisCache;
	@Resource
	private RedisTemplate<String, Object> redisTemplate;

	public RedisAuthorized()
	{
	}

	@Override
	public final String login(String name, String secret)
	{
		Assert.hasText(name, "用户名不能为空");
		Assert.hasText(secret, "密钥不能为空");

		BoundHashOperations<String, Object, Object> opt = redisTemplate.boundHashOps(ACCOUNT_NAME);
		String s = (String)opt.get(name);
		if (s == null || s.isEmpty())
		{
			try
			{
				redisCache.lock(ACCOUNT_NAME);
				if (s == null || s.isEmpty())
				{
					s = this.getAccount(name);
				}
				if (s != null && !s.isEmpty())
				{
					opt.put(name, s);
				}
			}
			finally
			{
				redisCache.unlock(ACCOUNT_NAME);
			}
		}

		if (secret.equals(s)) // 登录成功
		{
			String token = redisCache.get(ACCOUNT_TOKEN + name);
			if (token != null && !token.isEmpty())
			{
				redisCache.set(TOKEN_ACCOUNT + token, name, 1, TimeUnit.MINUTES);
			}

			token = UUID.randomUUID().toString();
			redisCache.set(ACCOUNT_TOKEN + name, token, expire, TimeUnit.SECONDS);
			redisCache.set(TOKEN_ACCOUNT + token, name, expire, TimeUnit.SECONDS);
			logger.info("登录成功：name={}, token={}", name, token);
			return token;
		}

		return null;
	}

	@Override
	public final boolean verify(String token)
	{
		Assert.hasText(token, "令牌不能为空");
		String name = redisCache.get(TOKEN_ACCOUNT + token);
		return name != null && !name.isEmpty();
	}

	@SuppressWarnings("unchecked")
	@Override
	public final boolean auth(String token, String url)
	{
		Set<String> set = this.noAuth();
		if (set != null && set.contains(url))
			return true;

		Assert.hasText(token, "令牌不能为空");
		Assert.hasText(url, "链接不能为空");

		String name = redisCache.get(TOKEN_ACCOUNT + token);
		if (name == null || name.isEmpty())
			return false;

		BoundHashOperations<String, Object, Object> optRole = redisTemplate.boundHashOps(ACCOUNT_ROLE);
		List<String> roleList = (List<String>)optRole.get(name);
		if (roleList == null || roleList.isEmpty())
		{
			try
			{
				redisCache.lock(ACCOUNT_ROLE);
				if (roleList == null || roleList.isEmpty())
				{
					roleList = this.listAccountRole(name);
				}
				if (roleList != null && !roleList.isEmpty())
				{
					optRole.put(name, roleList);
				}
			}
			finally
			{
				redisCache.unlock(ACCOUNT_ROLE);
			}
			if (roleList == null || roleList.isEmpty())
				return false;

		}

		BoundHashOperations<String, Object, Object> optUrl = redisTemplate.boundHashOps(ROLE_URL);
		for (String role : roleList)
		{
			Set<String> urlSet = (Set<String>)optUrl.get(role);
			if (urlSet == null || urlSet.isEmpty())
			{
				List<String> urlList = null;
				try
				{
					redisCache.lock(ROLE_URL);
					if (urlSet == null || urlSet.isEmpty())
					{
						urlList = this.listRoleUrl(role);
					}
					if (urlList != null && !urlList.isEmpty())
					{
						urlSet = new HashSet<>();
						urlSet.addAll(urlList);
						optUrl.put(role, urlSet);
					}
				}
				finally
				{
					redisCache.unlock(ROLE_URL);
				}
				if (urlList == null || urlList.isEmpty())
					continue;

			}

			if (urlSet.contains(url))
			{
				logger.info("有权限访问：{}", url);
				return true;
			}
		}

		return false;
	}

	@Override
	public final void clearSecret()
	{
		redisCache.delete(ACCOUNT_NAME);
	}

	@Override
	public final void clearAuth()
	{
		redisCache.delete(ACCOUNT_ROLE);
		redisCache.delete(ROLE_URL);
	}

	/**
	 * 返回不需要认证的路径
	 *
	 * @return
	 */
	protected Set<String> noAuth()
	{
		Set<String> set = new HashSet<>();
		set.add("/token");
		return set;
	}

	/**
	 * 通过帐号名称获取帐号密钥
	 *
	 * @param name 帐号名称
	 * @return 帐号密钥，不存在返回 null
	 */
	protected abstract String getAccount(String name);

	/**
	 * 通过帐号名称获取角色列表
	 *
	 * @param name 帐号名称
	 * @return 角色列表，不存在返回 null
	 */
	protected abstract List<String> listAccountRole(String name);

	/**
	 * 通过角色获取URL列表
	 *
	 * @param role 角色
	 * @return URL列表，不存在返回 null
	 */
	protected abstract List<String> listRoleUrl(String role);

}
